package com.zxhhyj.storm.dsl

import com.zxhhyj.storm.database.Database
import com.zxhhyj.storm.database.sqlite.SQLiteStatement
import com.zxhhyj.storm.schema.Column
import com.zxhhyj.storm.schema.DataType
import com.zxhhyj.storm.schema.Table

class QueryBuilder<T, E : Table<T>> internal constructor(
    internal val targetDatabase: Database,
    internal val targetTable: E,
    internal val selectColumns: List<Column<*, *>>
) {

    private val sqlStringBuilder = StringBuilder()

    init {
        sqlStringBuilder.append("SELECT ${selectColumns.joinToString { it.fieldName }}")
        sqlStringBuilder.append(" ")
        sqlStringBuilder.append("FROM ${targetTable.tableName}")
        sqlStringBuilder.append(" ")
    }

    fun where(block: E.() -> ConditionalExpression): QueryBuilder<T, E> {
        val expression = block(targetTable)
        val sql = expression.build()
        sqlStringBuilder.appendLine()
        sqlStringBuilder.append("WHERE")
        sqlStringBuilder.append(" ")
        sqlStringBuilder.append(sql)
        return this
    }

    fun groupBy(vararg selectColumns: Column<T, *>): QueryBuilder<T, E> {
        sqlStringBuilder.appendLine()
        sqlStringBuilder.append("GROUP BY")
        sqlStringBuilder.append(" ")
        val args = selectColumns.joinToString { it.fieldName }
        sqlStringBuilder.append(args)
        return this
    }

    fun asKotlinSequence() = Sequence { iterator() }

    operator fun iterator() = object : Iterator<ResultSet> {
        private val queryIterator by lazy {
            ResultSetIterator(targetDatabase.prepare(sqlStringBuilder.toString()))
        }

        override fun hasNext(): Boolean {
            return queryIterator.hasNext()
        }

        override fun next(): ResultSet {
            return queryIterator.next()
        }
    }

//    fun toList(): List<T> {
//        val statement = targetDatabase.prepare(sqlStringBuilder.toString())
//        val maps: MutableList<Map<String, Any?>> = mutableListOf()
//
//        while (statement.step()) {
//            val row = mutableMapOf<String, Any?>()
//
//            for (i in 0 until statement.getColumnCount()) {
//                val columnName = statement.getColumnName(i)
//                val column = selectColumns.find { it.fieldName == columnName }
//
//                row[columnName] = when (column?.kotlinType) {
//                    String::class -> {
//                        statement.getText(i)
//                    }
//
//                    Int::class -> {
//                        statement.getInt(i)
//                    }
//
//                    Long::class -> {
//                        statement.getLong(i)
//                    }
//
//                    Float::class -> {
//                        statement.getFloat(i)
//                    }
//
//                    ByteArray::class -> {
//                        statement.getBlob(i)
//                    }
//
//                    else -> {
//                        null
//                    }
//                }
//            }
//
//            maps.add(row)
//        }
//
//        statement.close()
//
//        val result = maps.map {
//            packing(targetTable, selectColumns, it)
//        }
//
//        return result
//    }

    class ResultSetIterator(private val sqliteStatement: SQLiteStatement) : Iterator<ResultSet> {

        private var hasNext = sqliteStatement.step()

        private val columnCount = sqliteStatement.getColumnCount()

        override fun hasNext(): Boolean {
            return hasNext
        }

        @Suppress("UNCHECKED_CAST")
        override fun next(): ResultSet {
            if (!hasNext) throw NoSuchElementException()
            val row = mutableMapOf<String, Any?>()
            for (i in 0 until columnCount) {
                val columnName = sqliteStatement.getColumnName(i)
                val value = when {
                    sqliteStatement.isNull(i) -> null
                    sqliteStatement.getColumnType(i) == DataType.BLOB -> sqliteStatement.getBlob(i)
                    sqliteStatement.getColumnType(i) == DataType.INTEGER -> sqliteStatement.getInt(i)
                    sqliteStatement.getColumnType(i) == DataType.REAL -> sqliteStatement.getDouble(i)
                    sqliteStatement.getColumnType(i) == DataType.TEXT -> sqliteStatement.getText(i)
                    else -> throw RuntimeException()
                }
                row[columnName] = value
            }
            hasNext = sqliteStatement.step()
            return ResultSet(row)
        }
    }

    class ResultSet(private val values: Map<String, *>) {

        @Suppress("UNCHECKED_CAST")
        operator fun <T> get(key: Column<*, T>): T {
            val value = values[key.fieldName] as T
            return (key.converter?.to?.invoke(value) ?: value) as T
        }

    }
}